# -*-coding:utf-8-*-

import handler.base
import pyrestful.rest
from pyrestful import mediatypes
from pyrestful.rest import get, post
import tornado.httpclient
import tornado.web
import tornado.gen
import tornado.ioloop
from tornado.web import HTTPError
from tornado.ioloop import PeriodicCallback
import json, time, uuid
from weixin.WXBizMsgCrypt import WXBizMsgCrypt
import xml.etree.cElementTree as ET
import settings
import qrcode, cStringIO
import urllib
import logging, traceback
import redis

PATH_API_WXQYH_GET_USERINFO = '/api/wxqyh/get_userinfo'  # 根据 code 获取 user_id
PATH_API_WXQYH_GET_USER = '/api/wxqyh/users/{user_id}'  # 根据 user_id 获取 user detail
PATH_API_WXQYH_GET_USERLIST = '/api/wxqyh/get_userlist'
PATH_API_WXQYH_SEND_MESSAGE = '/api/wxqyh/send_message'

logger = logging.getLogger()
redis_client = redis.Redis()


class Wxqyh(object):
    def get_user_id(self, code):
        url = self.__url_from_path(PATH_API_WXQYH_GET_USERINFO)
        url += '?code=%s' % code
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(url)
        response = json.loads(response.body)
        return response['UserId']

    def get_users(self):
        url = self.__url_from_path(PATH_API_WXQYH_GET_USERLIST)
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(url)
        response = json.loads(response.body)
        if 0 == response['errcode']:
            users = []
            for user_src in response['userlist']:
                user_dst = {}
                user_dst['user_id'] = user_src['userid']
                user_dst['name'] = user_src['name']
                if user_src.has_key('avatar'):
                    user_dst['head_img'] = user_src['avatar'] + '64'  # 64: 小头像
                else:
                    user_dst['head_img'] = ''
                users.append(user_dst)
            return users
        else:
            return []

    def send_news(self, title, desc, url, to_users):
        article = {}
        article['title'] = title
        article['description'] = desc
        article['url'] = url
        articles = []
        articles.append(article)
        news = {}
        news['articles'] = articles
        msg = {}
        msg['agentid'] = settings.WEIXIN_CORP_AGENT_ID
        msg['msgtype'] = 'news'
        msg['news'] = news
        msg['touser'] = '|'.join(to_users)
        msg = json.dumps(msg, ensure_ascii=False)
        url = self.__url_from_path(PATH_API_WXQYH_SEND_MESSAGE)
        http_request = tornado.httpclient.HTTPRequest(url, method='POST', body=msg)
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(http_request)
        response = json.loads(response.body)
        return 0 == response['errcode']

    def __url_from_path(self, path):
        return settings.WEIXIN_CORP_SERVICE_HOST + path


class WxqyhHandler(handler.base.BaseHandler):
    __token = None
    __token_expires_at = None
    __suite_token = None
    __suite_token_expires_at = None
    __suite_ticket = redis_client.get('suite_ticket')
    __waitings = {}
    __periodic = None

    def __init__(self, application, request, **kwargs):
        super(self.__class__, self).__init__(application, request, **kwargs)
        self.__wait_info = None
        if self.__class__.__periodic is None:
            self.__class__.__periodic = PeriodicCallback(self.__check_timeout, 1000)
            self.__class__.__periodic.start()

    @classmethod
    def __get_token(cls):
        # 记录超时的时间点，在其之前30秒就不再使用本地缓存的 access_token 而是从服务端请求更新
        # 如果判断说从服务端获取到的 access_token 与本的不同，则说明 access_token 已经拿到新
        # 的 token，则从下一次起使用本地缓存的 token
        if cls.__token_expires_at is None or 60 > cls.__token_expires_at - time.time():
            url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s'
            url = url % (settings.WEIXIN_CORP_ID, settings.WEIXIN_CORP_SECRET)
            http_client = tornado.httpclient.HTTPClient()
            response = http_client.fetch(url)
            response = json.loads(response.body)
            if not response.has_key('access_token'):
                raise HTTPError(401)
            if cls.__token is None or cls.__token != response['access_token']:
                cls.__token = response['access_token']
                cls.__token_expires_at = time.time() + response['expires_in']
        return cls.__token

    @classmethod
    def __get_suite_token(cls):
        # POST https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token
        # {
        #     "suite_id":"id_value" ,
        #     "suite_secret": "secret_value",
        #     "suite_ticket": "ticket_value"
        # }

        # {
        #     "suite_access_token":"61W3mEpU66027wgNZ_MhGHNQDHnFATkDa9-2llqrMBjUwxRSNPbVsMmyD-yq8wZETSoE5NQgecigDrSHkPtIYA",
        #     "expires_in":7200
        # }
        if cls.__suite_token_expires_at is None or 60 > cls.__suite_token_expires_at - time.time():
            suite_ticket = ''
            url = 'https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token'
            postdata = {'suite_id': settings.SUITE_ID,
                        'suite_secret': settings.SUITE_SECRET,
                        'suite_ticket': cls.__suite_ticket}
            postdata = json.dumps(postdata)
            http_request = tornado.httpclient.HTTPRequest(url, method='POST', body=postdata)
            http_client = tornado.httpclient.HTTPClient()
            response = http_client.fetch(http_request)
            response = json.loads(response.body)
            if not response.has_key('suite_access_token'):
                raise HTTPError(401)
            if cls.__suite_token is None or cls.__suite_token != response['suite_access_token']:
                cls.__suite_token = response['suite_access_token']
                cls.__suite_token_expires_at = time.time() + response['expires_in']
        return cls.__suite_token

    @get(_path=PATH_API_WXQYH_GET_USERINFO, _produces=mediatypes.APPLICATION_JSON)
    def __get_userinfo(self):
        code = self.get_argument('code')
        access_token = self.__get_token()
        url = 'https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s'
        url = url % (access_token, code)
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(url)
        return json.loads(response.body)

    @get(_path=PATH_API_WXQYH_GET_USER, _produces=mediatypes.APPLICATION_JSON)
    def __get_user(self, user_id):
        access_token = self.__get_token()
        url = 'https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s'
        url = url % (access_token, user_id)
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(url)
        return json.loads(response.body)

    @get(_path=PATH_API_WXQYH_GET_USERLIST, _produces=mediatypes.APPLICATION_JSON)
    def __get_userlist(self):
        access_token = self.__get_token()
        url = 'https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=%s'
        url += '&department_id=1&fetch_child=1&status=0'
        url = url % access_token
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(url)
        return json.loads(response.body)

    @post(_path=PATH_API_WXQYH_SEND_MESSAGE, _produces=mediatypes.APPLICATION_JSON)
    def __send_message(self):
        access_token = self.__get_token()
        url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s'
        url = url % access_token
        http_request = tornado.httpclient.HTTPRequest(url, method='POST', body=self.request.body)
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(http_request)
        return json.loads(response.body)

    @get(_path='/api/v2/authorize_qrcode', _produces=mediatypes.APPLICATION_JSON)
    def __authorize_qrcode(self):
        # redirect_uri=微信上确认登录的页面（其中有确认登录和取消按钮，点击确认时则调用 /api/v2/authorize_confirm?code=Code&state=STATE 接口并关闭当前页面
        # 点击取消按钮则直接关闭当前页面）
        redirect_uri = self.get_argument('redirect_uri')
        wait_state = str(uuid.uuid4())
        url = 'https://open.weixin.qq.com/connect/oauth2/authorize'
        url += '?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=%s'
        # TODO: 不同的企业号需要返填入不一样的 corp_id
        url = url % (settings.WEIXIN_CORP_ID, redirect_uri, wait_state)
        img = qrcode.make(url)
        output = cStringIO.StringIO()
        img.save(output, 'PNG')
        img_data = output.getvalue()
        output.close()
        self.set_header('Content-Type', 'image/png')
        # self.set_cookie('wait_state', wait_state, path='/api/v2/authorize_wait', expires=time.time() + 60)
        # self.set_cookie('wait_state', wait_state, path='/api/v2/authorize_wait')
        self.set_cookie('wait_state', wait_state, expires=time.time() + 60)
        self.finish(img_data)

    @tornado.web.asynchronous
    @get(_path='/api/v2/authorize_wait', _produces=mediatypes.APPLICATION_JSON)
    def __authorize_wait(self):
        wait_state = self.get_cookie('wait_state')
        if wait_state is None:
            self.set_status(400)
            self.finish()
            return
        self.__wait_info = {}
        self.__wait_info['wait_state'] = wait_state
        self.__wait_info['expires_at'] = time.time() + 60
        self.__wait_info['on_confirm'] = self.__on_confirm
        self.__wait_info['on_timeout'] = self.__on_timeout
        self.__class__.__waitings[wait_state] = self.__wait_info

    # /api/v2/authorize_confirm?code=CODE&state=STATE
    @get(_path='/api/v2/authorize_confirm', _produces=mediatypes.APPLICATION_JSON)
    def __authorize_confirm(self):
        state = self.get_argument('state')
        auth_info = self.__get_userinfo()
        logger.debug('auth_info: %s' % json.dumps(auth_info))
        if auth_info.has_key('UserId'):
            try:
                if self.__class__.__waitings.has_key(state):
                    user_info = self.__get_user(auth_info['UserId'])
                    logger.debug('user_info: %s' % json.dumps(user_info))
                    info = {}
                    info['user_id'] = user_info['userid']
                    info['name'] = user_info['name']
                    if user_info.has_key('avatar'):
                        info['head_img'] = user_info['avatar'] + '64'  # 64: 小头像
                    else:
                        info['head_img'] = ''
                    wait_info = self.__class__.__waitings[state]
                    wait_info['on_confirm'](state, info)
            except:
                logger.debug(traceback.format_exc())

    @classmethod
    def __check_timeout(cls):
        cur_time = time.time()
        expired_states = []
        for wait_info in cls.__waitings.itervalues():
            if cur_time > wait_info['expires_at']:
                expired_states.append(wait_info['wait_state'])
                wait_info['on_timeout']()
        for expired_state in expired_states:
            del cls.__waitings[expired_state]

    def __on_confirm(self, wait_state, info):
        del self.__class__.__waitings[wait_state]
        self.finish(info)

    def __on_timeout(self):
        raise HTTPError(408)

    def on_connection_close(self):
        if self.__wait_info is None:
            return
        wait_state = self.__wait_info['wait_state']
        if self.__class__.__waitings.has_key(wait_state):
            del self.__class__.__waitings[wait_state]

    @get(_path='/api/v2/suite/{action}', _produces=mediatypes.APPLICATION_JSON)
    def __suite_action(self, action):
        # https://qy.weixin.qq.com/cgi-bin/loginpage
        # ?suite_id=$suite_id$&pre_auth_code=$pre_auth_code$&redirect_uri=$redirect_uri$&state=$state$
        if 'get_auth_page_url' == action:
            url = 'https://qy.weixin.qq.com/cgi-bin/loginpage'
            url += '?suite_id=%s&pre_auth_code=%s&redirect_uri=%s&state=%s'
            pre_auth_code = self.__get_pre_auth_code()
            redirect_uri_default = 'http://%s/api/v2/suite/callback_auth_code' % settings.REPORT_HOST
            redirect_uri_default = urllib.quote_plus(redirect_uri_default)
            redirect_uri = self.get_argument('redirect_uri', redirect_uri_default)
            state = self.get_argument('state', 'STATE')
            url = url % (settings.SUITE_ID, pre_auth_code, redirect_uri, state)
            return {'url': url}
        elif 'callback_auth_code' == action:
            auth_code = self.get_argument('auth_code')
            expires_in = self.get_argument('expires_in')
            state = self.get_argument('state')
            self.__get_permanent_code(auth_code)
        else:
            logger.warning('GET /api/v2/suite/{action}: unknown action %s' % action)

    def __get_pre_auth_code(self):
        # POST https://qyapi.weixin.qq.com/cgi-bin/service/get_pre_auth_code?suite_access_token=SUITE_ACCESS_TOKEN
        # {
        # "suite_id":"id_value",
        # "appid":[id1,id2,id3]
        # }

        # {
        #     "errcode":"0" ,
        #     "errmsg":"ok" ,
        #     "pre_auth_code":"Cx_Dk6qiBE0Dmx4EmlT3oRfArPvwSQ-oa3NL_fwHM7VI08r52wazoZX2Rhpz1dEw",
        #     "expires_in":1200
        # }
        url = 'https://qyapi.weixin.qq.com/cgi-bin/service/get_pre_auth_code'
        url += '?suite_access_token=%s' % self.__get_suite_token()
        postdata = {'suite_id': settings.SUITE_ID}
        postdata = json.dumps(postdata)
        http_request = tornado.httpclient.HTTPRequest(url, method='POST', body=postdata)
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(http_request)
        response = json.loads(response.body)
        return response['pre_auth_code']

    def __get_permanent_code(self, auth_code):
        # POST https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code?suite_access_token=SUITE_ACCESS_TOKEN
        # {
        #     "suite_id":"id_value" ,
        #     "auth_code": "auth_code_value"
        # }
        url = 'https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code'
        url += '?suite_access_token=%s' % self.__get_suite_token()
        postdata = {'suite_id': settings.SUITE_ID, 'auth_code': auth_code}
        postdata = json.dumps(postdata)
        http_request = tornado.httpclient.HTTPRequest(url, method='POST', body=postdata)
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(http_request)
        logger.debug('get_permanent_code:\n%s' % response.body)
        info = json.loads(response.body)

        # TODO: access_token 管理

        access_token = info['access_token']
        corp_id = info['auth_corp_info']['corpid']
        agents = info['auth_info']['agent']
        for agent in agents:
            agent_id = agent['agentid']
            if settings.SUITE_APP_ID_REPORT == agent['appid']:
                redirect_uri = 'http://%s/weixin/share_list.html?corpid=%s' % (settings.REPORT_HOST, corp_id)
                redirect_uri = urllib.quote_plus(redirect_uri)
                url = 'https://open.weixin.qq.com/connect/oauth2/authorize'
                url += '?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base#wechat_redirect'
                url = url % (corp_id, redirect_uri)
                button = {'type': 'view', 'name': '分享列表', 'url': url}
                buttons = [button]
                menu = {'button': buttons}
                self.__set_agent_menu(json.dumps(menu, ensure_ascii=False), access_token, agent_id)

    def __set_agent_menu(self, menu, access_token, agent_id):
        url = 'https://qyapi.weixin.qq.com/cgi-bin/menu/create'
        url += '?access_token=%s&agentid=%s' % (access_token, agent_id)
        http_request = tornado.httpclient.HTTPRequest(url, method='POST', body=menu)
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(http_request)
        logger.debug('set agent menu response: %s' % response.body)

    def __get_auth_info(self):
        # POST https://qyapi.weixin.qq.com/cgi-bin/service/get_auth_info?suite_access_token=SUITE_ACCESS_TOKEN
        # {
        #     "suite_id":"suite_id_value",
        #     "auth_corpid": "auth_corpid_value",
        #     "permanent_code": "code_value"
        # }
        pass

    def __get_corp_token(self, auth_corpid, permanent_code):
        # POST https://qyapi.weixin.qq.com/cgi-bin/service/get_corp_token?suite_access_token=SUITE_ACCESS_TOKEN
        # {
        #     "suite_id":"suite_id_value",
        #     "auth_corpid": "auth_corpid_value",
        #     "permanent_code": "code_value"
        # }
        url = 'https://qyapi.weixin.qq.com/cgi-bin/service/get_corp_token'
        url += '?suite_access_token=%s' % self.__get_suite_token()
        postdata = {'suite_id': settings.SUITE_ID,
                    'auth_corpid': auth_corpid,
                    'permanent_code': permanent_code}
        postdata = json.dumps(postdata)
        http_request = tornado.httpclient.HTTPRequest(url, method='POST', body=postdata)
        http_client = tornado.httpclient.HTTPClient()
        response = http_client.fetch(http_request)
        # {
        #     "access_token": "xxxxxx", 
        #     "expires_in": 7200, 
        # }
        return json.loads(response.body)

    @post(_path='/api/v2/suite/receive', _produces=mediatypes.APPLICATION_JSON)
    def __suite_receive(self):
        try:
            msg_signature = self.get_argument('msg_signature')
            timestamp = self.get_argument('timestamp')
            nonce = self.get_argument('nonce')
            wxcpt = WXBizMsgCrypt(settings.SUITE_CB_TOKEN, settings.SUITE_CB_ENCODING_AES_KEY, settings.SUITE_ID)
            ret, msg = wxcpt.DecryptMsg(self.request.body, msg_signature, timestamp, nonce)
            if 0 == ret:
                xml_tree = ET.fromstring(msg)
                info_type = xml_tree.find('InfoType').text
                if 'suite_ticket' == info_type:
                    suite_ticket = xml_tree.find('SuiteTicket').text
                    logger.info('old ticker: %s, new ticker: %s' % (self.__class__.__suite_ticket, suite_ticket))
                    self.__class__.__suite_ticket = suite_ticket
                    redis_client.set('suite_ticket', suite_ticket)
                elif 'change_auth' == info_type:
                    suite_id = xml_tree.find('SuiteId').text
                    auth_corp_id = xml_tree.find('AuthCorpId').text
                    logger.debug('change_auth, suite_id: %s, auth_corp_id: %s' % (suite_id, auth_corp_id))
                elif 'cancel_auth' == info_type:
                    suite_id = xml_tree.find('SuiteId').text
                    auth_corp_id = xml_tree.find('AuthCorpId').text
                    logger.debug('cancel_auth, suite_id: %s, auth_corp_id: %s' % (suite_id, auth_corp_id))
            self.finish('success')
        except:
            logger.error(traceback.format_exc())

    @post(_path='/api/v2/suite/app/receive', _produces=mediatypes.APPLICATION_JSON)
    def __app_receive(self):
        try:
            corp_id = self.get_argument('corpid')
            msg_signature = self.get_argument('msg_signature')
            timestamp = self.get_argument('timestamp')
            nonce = self.get_argument('nonce')
            # wxcpt = WXBizMsgCrypt(settings.SUITE_CB_TOKEN, settings.SUITE_CB_ENCODING_AES_KEY, settings.SUITE_ID)
            wxcpt = WXBizMsgCrypt(settings.SUITE_CB_TOKEN, settings.SUITE_CB_ENCODING_AES_KEY, corp_id)
            ret, msg = wxcpt.DecryptMsg(self.request.body, msg_signature, timestamp, nonce)
            if 0 == ret:
                xml_tree = ET.fromstring(msg)
                event = xml_tree.find('Event').text
                if 'view' == event:
                    user_id = xml_tree.find('FromUserName').text
            logger.debug('/api/v2/suite/app/receive:\n%s' % msg)
            self.finish('success')
        except:
            logger.error(traceback.format_exc())
